/*
SYNOPSIS:

       ping  [ -c count]  [ -i interval]  [ -s packetsize]  ip_destination


FUNCIONAMIENTO:

    Envia <count> paquetes a <ip_destination> 
          con tamano <packetsize> y 
          intervalo entre cada envio de <interval> segundos.

          Por defecto count es 5, interval es 2 seg. y packetsize 
          es el minimo exigido por ICMP.

*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <ctype.h>
#include "memAndSem.h"
#include "icmp.h"
#include "nivelRed.h"

#define PING_ERR -1
#define PING_OK 0

#define DEFAULT_COUNT 5
#define DEFAULT_INTERVAL 2
#define DEFAULT_PACKETSIZE 0

#define PING_ID 314

typedef struct{
	struct timeval sendTime;	
} ECHO_DATA;

typedef struct __pingData{
	int size;
	ECHO_DATA *echoArray;
} *PING_DATA;

typedef enum{COUNT, INTERVAL, PACKETSIZE, UNDEF} PARAMETER_TYPE;


PING_DATA pingData=NULL;
short pingOperative=0;
int nReceived=0;
int nSend=0;
short sleeping=0;
pthread_mutex_t sleeping_mutex = PTHREAD_MUTEX_INITIALIZER;

#define SEC_USEC_CONV (1e9)
#define MSEC_USEC_CONV (1e3)
#define GET_USEC(time) (((time).tv_sec)*SEC_USEC_CONV+(time).tv_usec)
#define DIFF_TIME(tEnd, tStart) (GET_USEC((tEnd))-GET_USEC((tStart)))
#define USEC_TO_MSEC(time) (((double)(time))/MSEC_USEC_CONV)
#define PING_LATENCY (10)

int getParameters(int argc, char **argv, int *count, int *interval, int *packetsize, BYTE *ipDst);
BYTE *createData(int packetsize);
PING_DATA createPingData(int nEntries);
void freePingData(PING_DATA *pData);
void insertPingData(PING_DATA pData, int seq);
ECHO_DATA *extractEchoDataPingData(PING_DATA pData, int seq);
PARAMETER_TYPE getType(char *string, int *paramData);
int sleepUntilEndReception();
int pingNotify(BYTE *dirSrc, unsigned int seq, unsigned int size, BYTE *data, op_ip *opt);
  
int pingNotify(BYTE *dirSrc, unsigned int seq, unsigned int size, BYTE *data, op_ip *opt)
{
	ECHO_DATA *echoData=NULL;
	struct timeval curTime;
	
	if(!pingOperative){
		printf("Rechazado Echo Rep desde %d.%d.%d.%d por modulo inoperativo\n", dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3]);
		return PING_ERR;
	}
	echoData=extractEchoDataPingData(pingData, seq);
	if(data==NULL || echoData==NULL){
		printf("Rechazado Echo Rep desde %d.%d.%d.%d\n", dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3]);
		return PING_ERR;
	}
	
	nReceived++;
	gettimeofday(&curTime, NULL);
	
	printf("Recibidos %d bytes desde (%d.%d.%d.%d) -> Seq=%d - Elapsed time=%f mseg\n", size,
			dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3], seq, USEC_TO_MSEC(DIFF_TIME(curTime, echoData->sendTime)));
	
	pthread_mutex_lock(&sleeping_mutex);
	{
		if(nReceived==nSend && sleeping){
			sleeping=0;
			wakeup(getpid());
		}
	}
	pthread_mutex_unlock(&sleeping_mutex);
	
	return PING_OK;
}

int main(int argc, char **argv)
{
	int count, interval, packetsize;
	BYTE ipDst[IP_ALEN];
	int i=0;
	short status=PING_OK;
	BYTE *data=NULL;
	
	if(getParameters(argc, argv, &count, &interval, &packetsize, ipDst)!=PING_OK){
		printf("Error de parametros de entrada: ping [-c count] [-i interval] [-s packetsize] ip_destination\n");
		return PING_ERR;
	}
	
	if(nivelRed_inicializa()!=0){
		printf("Error de inicializacion del Nivel de Red\n");
		return PING_ERR;
	}
	
	pingData=createPingData(count);
	nSend=count;
	
	pingOperative=1;
	/*
	int icmp_envia_echo(BYTE *dir_ip_destino, unsigned int id,
  unsigned int seq, pf_notificacion_echo callback,
  unsigned int tamano, BYTE *datos, op_ip *opciones);
  
 */
	data=createData(packetsize);
	for(i=0; i<count; i++){
		insertPingData(pingData, i);
		if(icmp_envia_echo(ipDst, PING_ID, i, pingNotify, packetsize, data, NULL)!=0){
			printf("Error de envio del ping %d\n", i+1);
			free(data);
			freePingData(&pingData);
			nivelRed_finaliza();
			return PING_ERR;
		}
		
		sleep(interval);
	}
	
	free(data);
	
	puts("Iniciando espera\n");
	/*
	pthread_mutex_lock(&sleeping_mutex);
	{
			if(sleepUntilWakeup(PING_LATENCY)==MEM_AND_SEM_TIMEOUT){
				printf("Agotado el tiempo de espera por respuesta\n");
				status=PING_ERR;
			}
	}
	pthread_mutex_unlock(&sleeping_mutex);
	*/
	
	status=sleepUntilEndReception();
	
	puts("Finalizada espera\n");
	pingOperative=0;
	
	if(nivelRed_finaliza()!=0){
		printf("Error al finalizar el Nivel de Red\n");
		status=PING_ERR;
	}
	
	freePingData(&pingData);
	
	printf("-----RESUMEN-----\n\tEnviados: %d - Recibidos: %d - Perdidos: %.2f %%\n", count, nReceived, ((double)(count-nReceived))/(count)*100);
	
	return status;
}

int getParameters(int argc, char **argv, int *count, int *interval, int *packetsize, BYTE *ipDst)
{
	int i, paramData;
	PARAMETER_TYPE type;
	
	if(argc<2){
		return PING_ERR;
	}
	
	if(lee_cadena_ip(argv[argc-1], ipDst)!=0){
		return PING_ERR;
	}
	
	*count=DEFAULT_COUNT;
	*interval=DEFAULT_INTERVAL;
	*packetsize=DEFAULT_PACKETSIZE;
	
	for(i=1; i<argc-1; i++){
		type=getType(argv[i], &paramData);
		switch(type){
			case COUNT:
				*count=paramData;
				break;
			case INTERVAL:
				*interval=paramData;
				break;
			case PACKETSIZE:
				*packetsize=paramData;
				break;
			case UNDEF:
				return PING_ERR;
			default:
				return PING_ERR;
		}
	}
	
	return PING_OK;
}

BYTE *createData(int packetsize)
{
	BYTE *data=NULL;
	
	data=(BYTE *)malloc(packetsize*sizeof(BYTE));

	return data;
}

PING_DATA createPingData(int nEntries)
{
	PING_DATA pData=NULL;
	
	pData=(struct __pingData *)calloc(1, sizeof(struct __pingData));
	if(pData==NULL){
		return NULL;
	}
	
	pData->echoArray=(ECHO_DATA *)calloc(nEntries, sizeof(ECHO_DATA));
	if(pData->echoArray==NULL){
		free(pData);
		return NULL;
	}
	
	pData->size=nEntries;
	
	return pData;
}


void freePingData(PING_DATA *pData)
{
	if(pData==NULL || *pData==NULL){
		return;
	}
	
	free((*pData)->echoArray);
	free(*pData);
	
	*pData=NULL;
}


void insertPingData(PING_DATA pData, int seq)
{
	if(pData==NULL || pData->echoArray==NULL || seq>pData->size){
		return;
	}

	gettimeofday(&(pData->echoArray[seq].sendTime), NULL);
	
	return;
}

ECHO_DATA *extractEchoDataPingData(PING_DATA pData, int seq)
{
	if(pData==NULL || pData->echoArray==NULL || seq>pData->size){
		return NULL;
	}

	return &(pData->echoArray[seq]);
}

PARAMETER_TYPE getType(char *string, int *paramData)
{
	PARAMETER_TYPE type=UNDEF;
	int i=2;
	
	if(string==NULL){
		return UNDEF;
	}
	
	if(string[0]!='-'){
		return UNDEF;
	}
	
	switch(string[1]){
		case 'c':
			type=COUNT;
			break;
			
		case 'i':
			type=INTERVAL;
			break;
		
		case 's':
			type=PACKETSIZE;
			break;
			
		default:
			return UNDEF;
	}

	while(string[i]!='\0' && isdigit(string[i])==0){
		i++;
	}
	
	if(string[i]=='\0'){
		return UNDEF;
	}
	
	*paramData=atoi(string+i);
	
	return type;
}

int sleepUntilEndReception()
{
	int status;
	
	pthread_mutex_lock(&sleeping_mutex);
	{
		if(nReceived==nSend){
			status=PING_OK;
		}
		else{
			sleeping=1;	
				
			if(sleepUntilWakeup(PING_LATENCY)==MEM_AND_SEM_TIMEOUT){
				printf("Agotado el tiempo de espera por respuesta\n");
				status=PING_ERR;
			}
		}
	}
	pthread_mutex_unlock(&sleeping_mutex);
	
	return status;
}

